using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Charisma.Linq;
using MongoDB.Driver;

namespace Charisma.MongoDb.Linq
{
    /// <summary>
    /// <see cref="IQueryProvider"/> implementation.
    /// </summary>
    internal sealed class QueryProvider : IQueryProvider
    {
        private static readonly MethodInfo executeMethod =
            typeof (QueryProvider)
                .GetMethods()
                .Single(mi => mi.Name == "Execute" && mi.IsGenericMethod);

        private readonly MongoDatabase database;
        private readonly string collectionName;

        internal QueryProvider(MongoDatabase database, string collectionName)
        {
            this.database = database;
            this.collectionName = collectionName;
        }

        public MongoDatabase Database
        {
            get { return database; }
        }

        public string CollectionName
        {
            get { return collectionName; }
        }

        #region IQueryProvider Members

        /// <inheritdoc/>
        IQueryable IQueryProvider.CreateQuery(Expression expression)
        {
            var elementType = ReflectionService.GetElementType(expression.Type);
            try
            {
                var query =
                    (IQueryable)
                    Activator.CreateInstance(typeof (Queryable<>).MakeGenericType(elementType), this, expression);
                return query;
            }
            catch (TargetInvocationException e)
            {
                throw e.InnerException;
            }
        }

        /// <inheritdoc/>
        public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
        {
            return new Queryable<TElement>(this, expression);
        }

        /// <inheritdoc/>
        object IQueryProvider.Execute(Expression expression)
        {
            var resultType = expression.Type.IsOfGenericInterface(typeof (IEnumerable<>))
                ? typeof (IEnumerable<>).MakeGenericType(expression.Type.GetGenericArguments())
                : expression.Type;
            return executeMethod.MakeGenericMethod(resultType).Invoke(this, new[] {expression});
        }

        /// <inheritdoc/>
        public TResult Execute<TResult>(Expression expression)
        {
            var context = new TranslatorContext(expression);
            var translator = new Translator(context);
            var query = translator.Translate<TResult>();
            var collection = database.GetCollection(collectionName);
            return query.Execute(collection);
        }

        #endregion

        [ContractInvariantMethod]
        private void ObjectInvariant()
        {
        }
    }
}